package bogus.core;

import java.awt.BorderLayout;
import java.awt.Panel;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

import owg.util.AwesomeAnimator;
import owg.util.awt.cursor.Cursors;
import owg.util.data.Pair;
import owg.util.opengl.EGL3;
import owg.util.opengl.TTFFontRenderer;
import owg.util.opengl.TextureLib;

import com.jogamp.opengl.GLAnimatorControl;
import com.jogamp.opengl.GLProfile;

import loader.MiniLoader;
import world.Global;
import world.World;
import bogus.settings.Settings;
import bogus.userInput.Input;
import bogus.minigame.*;
import audio.Audio;

public class BogusCore
{
    private static final World DEFAULT_MINIGAME;

    static
    {
	World def;
	try (BufferedReader bi = new BufferedReader(new FileReader("DEFAULT_MINIGAME.txt"));) 
	{
	    def = World.valueOf(bi.readLine());
	}
	catch(Exception e)
	{
	    if(e instanceof FileNotFoundException)
		System.out.println("No default minigame file, continuing");
	    else
	    {
		System.err.println("Warning: Could not parse default minigame file!");
		System.err.println("Valid values: "+Arrays.toString(World.values()));
		e.printStackTrace();
	    }
	    def = World.TEST;
	}

	DEFAULT_MINIGAME = def;
    }

    public static Panel panel;

    protected static BogusCanvas canvas;
    protected static BogusRenderer renderer;

    private static GLAnimatorControl animator;
    private static CoreParams coreParams;

    private static MinigameBase currentMinigame;

    public static EGL3 egl;
    public static BogusCore bogusCore;

    /**
     * @param parent The parent container. This object will add itself.
     * @param constraints The constraints with which to add this object to the parent container via the {@link Panel#add(java.awt.Component, Object)} method. 
     * Should be null if no constraints should be used.
     */
    public BogusCore(Panel parent, Object constraints)
    {
	panel = new Panel();
	panel.setLayout(new BorderLayout());

	TTFFontRenderer.FONT_DIRECTORY = Global.fileRelative("res/fonts/");

	Queue<Pair<String, Runnable>> jobs = new LinkedList<>();
	jobs.add(new Pair<>("Initializing OpenGL", () -> {GLProfile.initSingleton();}));

	jobs.add(new Pair<>("Initializing Audio System", new Runnable()
	{
	    @Override
	    public void run()
	    {
		Audio.init(Settings.settings.useOpenAL, Settings.settings.jsAudioBufferMultiplier, 1, 1, Global.MUSIC_DIRECTORY);
		Audio.loadDir(Global.fileRelative("res/sounds").getAbsolutePath());
	    }
	}));

	jobs.add(new Pair<>("Loading Texture Data", new Runnable()
	{
	    @Override
	    public void run()
	    {
		TextureLib.loadDir(Global.fileRelative("res/sprites").getAbsolutePath(), Settings.settings.forcePOTS);
	    }
	}));

	panel.add(new LoadingPanel(this, jobs), BorderLayout.CENTER);
	parent.add(panel, constraints);
    }

    /**The GL Canvas and Renderer have been created and are ready for display.*/
    protected static void loadingCompleted(BogusCanvas canvas, BogusRenderer renderer)
    {
	coreParams = new CoreParams();

	BogusCore.canvas = canvas;
	BogusCore.renderer = renderer;

	panel.removeAll();
	panel.invalidate();
	panel.add(canvas, BorderLayout.CENTER);
	panel.validate();
	panel.repaint();

	Input.registerMouse(canvas);
    }
    /**Returns the GLCanvas. Note that this will be null during boot.*/
    public static BogusCanvas getCanvas()
    {
	return canvas;
    }
    /**Call to unload the current minigame and load the minigame represented by the indicated world enum. 
     * This will start a new loading sequence, causing the current minigame to become a {@link MiniLoader}.*/
    public static void loadMinigame(World world){
	setMinigame(new MiniLoader(world));
    }
    /**Return the current minigame instance.<br>
     * If a minigame has not been loaded before the first call to this method, then the loading sequence for {@link #DEFAULT_MINIGAME} will be initialized. 
     * Note that this will be a {@link MiniLoader} instance during the loading sequence for any particular minigame.
     * */
    public static MinigameBase getMinigame(){
	if(currentMinigame == null)
	    loadMinigame(DEFAULT_MINIGAME);
	return currentMinigame;
    }
    /**Sets the current minigame instance. Used internally.*/
    private static void setMinigame(MinigameBase minigame){
	if(animator != null){
	    animator.stop();
	    animator.remove(canvas);
	}
	assert minigame != null : "Cannot set minigame to null";

	if(currentMinigame != null)
	{
	    egl.modelviewMatrix().pop();
	    egl.projectionMatrix().pop();
	    egl.textureMatrix().pop();
	    egl.popAttribs();
	    egl.setViewport(0, 0, canvas.getWidth(), canvas.getHeight());
	    currentMinigame.dispose();
	}
	egl.modelviewMatrix().push();
	egl.projectionMatrix().push();
	egl.textureMatrix().push();
	egl.pushAttribs();

	currentMinigame = minigame;
	currentMinigame.init();

	coreParams = currentMinigame.getCoreParams(); 
	Audio.setConstants(coreParams.targetSPS, 1, Global.MUSIC_DIRECTORY);
	Input.setConstants(coreParams.preferredMouseDimension);
	if (coreParams.mouseVisible)
	    canvas.setCursor(Cursors.DEFAULT_CURSOR);
	else
	    canvas.setCursor(Cursors.NO_CURSOR);
	animator = new AwesomeAnimator(canvas, true, coreParams.targetFPS);
	renderer.restartClock();
	animator.start();
    }
    /**Called when the loading sequence for a minigame has completed.*/
    public static void loadingCompleted(MinigameBase instance) {
	setMinigame(instance);
    }

    public static int getTargetFPS()
    {
	return coreParams.targetFPS;
    }
    public static int getActualFPS()
    {
	return (int)Math.round(animator.getLastFPS());
    }
    public static int getTargetSPS()
    {
	return coreParams.targetSPS;
    }
    public static int getActualSPS()
    {
	return (int)Math.round(renderer.getLastSPS());
    }
    public static float getInterp()
    {
	return renderer.getInterp();
    }
}
